package com.gitee.tje.waving.factory;

import com.gitee.tje.waving.beans.*;
import com.gitee.tje.waving.beans.exception.BeanCreationException;
import com.gitee.tje.waving.beans.exception.BeanDefinitionException;
import com.gitee.tje.waving.factory.config.PropertiesEditor;

import java.util.*;

/**
 * 默认的bean工厂
 *
 * @author taojiaen
 * @date 2018-01-22 08:09
 */
public class BaseBeanFactory implements BeanFactory {
    private Map<String, FormatBeanDefinition> beanDefinitions = new HashMap<>();
    private NamedBeanMap namedBeanMap = new NamedBeanMap();
    private List<BeanProcessor> beanProcessors = new ArrayList<>();
    private List<PropertiesEditor> propertyEditors = new ArrayList<>();
    private ClassLoader currentClassLoader;
    private final BeanFactory parent;

    public BaseBeanFactory(ClassLoader classLoader) {
        this(null, classLoader);
    }

    public BaseBeanFactory(BeanFactory parent, ClassLoader classLoader) {
        this.parent = parent;
        this.currentClassLoader = classLoader;
    }


    @Override
    public synchronized Object getBean(String beanName) {
        Object ret = doGetBean(beanName);
        if (parent != null && ret == null) {
            return parent.getBean(beanName);
        }
        return ret;
    }

    private Object doGetBean(String beanName) {
        if (!namedBeanMap.contains(beanName)) {
            return createBean(beanName);
        } else {
            return namedBeanMap.getBean(beanName);
        }
    }


    @Override
    public Collection<String> getBeanName(Class<?> clazz) {


        List<String> list = new ArrayList<>();

        for (FormatBeanDefinition formatBeanDefinition : beanDefinitions.values()) {
            if (clazz.isAssignableFrom(formatBeanDefinition.beanClazz)) {
                list.add(formatBeanDefinition.beanName);
            }
        }
        return list;
    }

    @Override
    public void createAllSingleObj() {
        for (FormatBeanDefinition definition : beanDefinitions.values()) {

            if (BeanProcessor.class.isAssignableFrom(definition.beanClazz)) {
                Object ret = getBean(definition.beanName);
                if (ret != null) {
                    beanProcessors.add((BeanProcessor) ret);
                }
            }

        }

        for (FormatBeanDefinition beanDefinition : beanDefinitions.values()) {
            if (beanDefinition.scope == Scope.SINGlETON) {
                getBean(beanDefinition.beanName);
            }
        }
    }

    private Object createBean(String beanName) {
        if (beanDefinitions.get(beanName) == null) {
            throw new NullPointerException("no such bean definition " + beanName);
        }

        FormatBeanDefinition beanDefinition = beanDefinitions.get(beanName);
        try {

            Object ret = createBean(beanDefinition);

            if (beanDefinition.scope == Scope.SINGlETON) {
                namedBeanMap.addBean(beanDefinition.beanName, ret);
            }


            return ret;
        } catch (Exception e) {
            throw new BeanCreationException(e);
        }
    }

    private Object createBean(FormatBeanDefinition beanDefinition) {

        Object ret;

        BeanWrapper wrapper;
        try {
            wrapper = new BeanWrapper(beanDefinition, beanDefinition.beanClazz.newInstance(), this);
            wrapper.populate();

            ret = wrapper.getInstance();
            for (BeanProcessor beanProcessor : beanProcessors) {
                ret = beanProcessor.processAfterInitiation(beanDefinition.beanName, beanDefinition.beanClazz, wrapper
                        .getInstance());
                if (ret == null) {
                    return null;
                }
            }
        } catch (InstantiationException | IllegalAccessException e) {
            throw new BeanCreationException(e);
        }

        return ret;
    }


    @Override
    public final synchronized void register(BeanDefinition beanDefinition) {
        try {
            Class<?> beanClass = currentClassLoader.loadClass(beanDefinition.beanClazz);
            beanDefinitions.put(beanDefinition.beanName, new FormatBeanDefinition(beanDefinition, beanClass));
        } catch (ClassNotFoundException e) {
            throw new BeanDefinitionException(e);
        }

    }


    public List<PropertiesEditor> getPropertyEditors() {
        return propertyEditors;
    }

    @Override
    public void destroy() {
        for (DestroyedBean destroyedBean : namedBeanMap.getBeans(DestroyedBean.class)) {
            destroyedBean.destory();
        }
        namedBeanMap.clear();
    }
}
